// The MIT License (MIT)
// Copyright (c) 2011 Akiyoshi Ochiai
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to 
// deal in the Software without restriction, including without limitation the 
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is 
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// 
#include "julius_driver.h"

#include "char_converter.h"

extern "C" {

void OnEventSpeechReady(Recog* recog, void* dummy) {
  using speech_recog_julius::JuliusDriver;
  JuliusDriver* const driver = static_cast<JuliusDriver* const>(dummy);
  try {
    if (recog->jconf->input.speech_input == SP_MIC || 
        recog->jconf->input.speech_input == SP_NETAUDIO) {
      driver->FireSpeechReady();
    }
  } catch (...) {
    driver->FireError();
  }
}


void OnResult(Recog* recog, void* dummy) {
  using namespace speech_recog_julius;

  JuliusDriver* const driver = static_cast<JuliusDriver* const>(dummy);
  try {
    CharConverter converter("EUC-JP", "UTF-8");

    for (RecogProcess* process = recog->process_list; 
         process != NULL; process = process->next) {
      if (!process->live)
        continue;

      if (process->result.status < 0) {
        switch (process->result.status) {
          case J_RESULT_STATUS_REJECT_POWER:
            driver->FireRecognitionFailure("input rejected by power.");
            break;
          case J_RESULT_STATUS_TERMINATE:
            driver->FireRecognitionFailure("input terminated by request.");
            break;
          case J_RESULT_STATUS_ONLY_SILENCE:
            driver->FireRecognitionFailure("input rejected by decoder"
                                           " (silence input result).");
            break;
          case J_RESULT_STATUS_REJECT_GMM:
            driver->FireRecognitionFailure("input rejected by GMM");
            break;
          case J_RESULT_STATUS_REJECT_SHORT:
            driver->FireRecognitionFailure("input rejected by short input");
            break;
          case J_RESULT_STATUS_FAIL:
            driver->FireRecognitionFailure("search failed");
            break;
          default:
            driver->FireRecognitionFailure("Unknown process result!");
        }
        continue;
      }

      WORD_INFO* word_info = process->lm->winfo;
      NBestSentences sentences;
      for (int i = 0; i < process->result.sentnum; ++i) {
        Sentence& result_sentence = process->result.sent[i];
        RecognizedSentence sentence;
        for (int i = 0;  i < result_sentence.word_num; ++i) {
          const char* word = word_info->woutput[result_sentence.word[i]];
          sentence.AddWord(converter.Convert(word), 
                           result_sentence.confidence[i]);
        }
        sentence.set_score(result_sentence.score);
        sentence.set_acoustic_model_score(result_sentence.score_am);
        sentence.set_language_model_score(result_sentence.score_lm);
        sentences.push_back(sentence);
      }
      driver->FireRecognitionSuccess(sentences);
    }
  } catch (...) {
    driver->FireError();
  }
}

} // end extern "C"


namespace speech_recog_julius {


JuliusDriver::JuliusDriver(std::string const& config_path) 
    : handler_(NULL),
      recog_(NULL) {
  ::jlog_set_output(NULL); // Disable logging
  ::Jconf* jconf = ::j_config_load_file_new(const_cast<char*>(config_path.c_str()));

  if (!jconf) 
    throw std::runtime_error("j_config_load_file_new() failed.");

  recog_ = ::j_create_instance_from_jconf(jconf);
  if (!recog_) {
    ::j_jconf_free(jconf);
    throw std::runtime_error("j_create_instance_from_jconf() failed.");
  }

  if (FALSE == ::j_adin_init(recog_)) {
    ::j_recog_free(recog_);
    ::j_jconf_free(jconf);
    throw std::runtime_error("j_adin_init() failed.");
  }

  ::callback_add(recog_, CALLBACK_EVENT_SPEECH_READY, &OnEventSpeechReady, this);
  ::callback_add(recog_, CALLBACK_RESULT, &OnResult, this);
}


JuliusDriver::~JuliusDriver() {
  ::j_recog_free(recog_);
}


void JuliusDriver::FireSpeechReady() {
  if (handler_)
    handler_->OnSpeechReady();
}


void JuliusDriver::FireRecognitionSuccess(NBestSentences const& sentences) {
  if (handler_)
    handler_->OnRecognitionSuccess(sentences);
}


void JuliusDriver::FireRecognitionFailure(std::string const& message) {
  if (handler_)
    handler_->OnRecognitionFailure(message);
}

void JuliusDriver::FireError() {
  if (handler_)
    handler_->OnError();
}


void JuliusDriver::RunLoop() {
  switch (::j_open_stream(recog_, NULL)) {
    case 0:
      break;  // success
    case -1:
      throw std::runtime_error("error in input stream.");
    case -2:
      throw std::runtime_error("failed to begin input stream.");
    default:
      throw std::logic_error("j_open_stream() returns a unknown return code.");
  }

  int retcode = 0;
  while (retcode != -1) {
    retcode = ::j_recognize_stream(recog_);
  }

  if (retcode == -1)
    ::j_close_stream(recog_);
}

void JuliusDriver::Shutdown() {
  ::j_close_stream(recog_);
}


} // end speech_recog_julius
